﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO.Ports;
using System.Linq;
using System.Reactive;
using System.Threading.Tasks;
using Avalonia.Threading;
using Microsoft.EntityFrameworkCore;
using MilkTeaMachine.OfflineClient.EntityFrameworkCore;
using MilkTeaMachine.OfflineClient.EntityFrameworkCore.Models;
using MilkTeaMachine.OfflineClient.Models;
using MilkTeaMachine.OfflineClient.Utils;
using NModbus;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Splat;

namespace MilkTeaMachine.OfflineClient.ViewModels;

public class IngredientCalibrationSelectorViewModel : ViewModelBase, IRoutableViewModel
{
    public IngredientCalibrationSelectorViewModel() : this(default!)
    {
    }

    private readonly ApplicationDbContext _db;

    public IngredientCalibrationSelectorViewModel(IScreen hostScreen)
    {
        HostScreen = hostScreen;
        SaveAndQuitCmd = ReactiveCommand.CreateFromTask(SaveAndQuitAsync);
#if DEBUG
        _db = new ApplicationDbContext(new DbContextOptionsBuilder<ApplicationDbContext>()
            .UseSqlite("Data Source=EntityFrameworkCore/DataBase/db.db").Options);
#else
        _db = Locator.Current.GetService<ApplicationDbContext>()!;
#endif
        _allIngredientList = _db.Ingredients.AsNoTracking().Where(ingredient =>
            ingredient.IngredientName != "冰水" &&
            ingredient.IngredientName != "冰块" &&
            ingredient.IngredientName != "热水").ToList();
        NotBindAddressIngredientObservableCollection = CreateComboBoxItemIngredientObservableCollection();
        InitializeComboBoxSelectedItems();
        GoIngredientInputCmd = ReactiveCommand.CreateFromObservable(() =>
            HostScreen.Router.Navigate.Execute(new IngredientInputViewModel(HostScreen)));
        PrePageCmd = ReactiveCommand.CreateFromTask(PrePage,
            this.WhenAnyValue(
                vm => vm.ShowExtended,
                showExtended => showExtended is true));
        NextPageCmd = ReactiveCommand.CreateFromTask(NextPage,
            this.WhenAnyValue(
                vm => vm.ShowExtended,
                showExtended => !showExtended));
        GoIngredientCalibrationCmd = ReactiveCommand.CreateFromTask(async (Ingredient ingredient) =>
        {
            await GoIngredientCalibrationAsync(ingredient);
        });
        GoIngredientCalibrationSpecificTypeCmd = ReactiveCommand.CreateFromObservable(
            (string typeString) =>
            {
                var typeEnum = Enum.Parse<WeightTypeEnum>(typeString);
                return HostScreen.Router.Navigate.Execute(new IngredientCalibrationViewModel(HostScreen, typeEnum));
            });
        GoFactorySettingsLoginCmd = ReactiveCommand.CreateFromObservable(
            () => HostScreen.Router.Navigate.Execute(new FactorySettingsLoginViewModel(HostScreen)));
        OutIngredientCmd = ReactiveCommand.CreateFromTask(async (string address) =>
        {
            await OutIngredientAsync(address);
        });
        InitializeOutSpeed();
    }

    private void InitializeOutSpeed()
    {
        var dictionary = _allIngredientList
            .Where(x => x.DeviceAddress != -1)
            .ToDictionary(x => x.DeviceAddress, x => x.OutSpeed);
        Address11OutSpeed = dictionary.GetValueOrDefault(11, 200);
        Address12OutSpeed = dictionary.GetValueOrDefault(12, 200);
        Address13OutSpeed = dictionary.GetValueOrDefault(13, 200);
        Address14OutSpeed = dictionary.GetValueOrDefault(14, 200);
        Address15OutSpeed = dictionary.GetValueOrDefault(15, 200);
        Address16OutSpeed = dictionary.GetValueOrDefault(16, 200);
        Address21OutSpeed = dictionary.GetValueOrDefault(21, 200);
        Address22OutSpeed = dictionary.GetValueOrDefault(22, 200);
        Address23OutSpeed = dictionary.GetValueOrDefault(23, 200);
        Address24OutSpeed = dictionary.GetValueOrDefault(24, 200);
        Address31OutSpeed = dictionary.GetValueOrDefault(31, 200);
        Address32OutSpeed = dictionary.GetValueOrDefault(32, 200);
        Address33OutSpeed = dictionary.GetValueOrDefault(33, 200);
        Address34OutSpeed = dictionary.GetValueOrDefault(34, 200);
        Address41OutSpeed = dictionary.GetValueOrDefault(41, 200);
        Address42OutSpeed = dictionary.GetValueOrDefault(42, 200);
        Address43OutSpeed = dictionary.GetValueOrDefault(43, 200);
        Address44OutSpeed = dictionary.GetValueOrDefault(44, 200);
        Address51OutSpeed = dictionary.GetValueOrDefault(51, 200);
        Address52OutSpeed = dictionary.GetValueOrDefault(52, 200);
        Address53OutSpeed = dictionary.GetValueOrDefault(53, 200);
        Address54OutSpeed = dictionary.GetValueOrDefault(54, 200);
    }

    private async Task OutIngredientAsync(string address)
    {
        var parser = new IniFileParser.IniFileParser();
        var data = parser.ReadFile("Configuration/config.ini");
        var acceleration = Convert.ToByte(data["Machine"]["acceleration"]);
        var direction = Convert.ToByte(data["Machine"]["direction"]);
        var directionAndAcceleration = App.GetComposedUshort(direction, acceleration);
        var turnsDic = GeInputDictionary();
        int gram;
        Ingredient? ingredient;
        using (var master = IModbusSerialFactory.Create())
        {
            if (int.TryParse(address, out var addressInt))
            {
                ingredient = await _db.Ingredients
                    .FirstOrDefaultAsync(ingredient1 => ingredient1.DeviceAddress == addressInt)
                    .ConfigureAwait(true);

                if (ingredient is null)
                {
                    await Dispatcher.UIThread.InvokeAsync(() => _ = MessageBoxUtil.GetBox("未找到该原料").ShowWindowAsync());
                    return;
                }

                var speed = Convert.ToUInt16(ingredient.OutSpeed);
                //获取转多少圈
                gram = turnsDic[addressInt];
                var needPulse = Convert.ToUInt32(gram / ingredient.TurnsPerGram * 3200);
                var pulseHigh = (ushort)((needPulse >> 16) & 0xFFFF); // 获取高16位
                var pulseLow = (ushort)(needPulse & 0xFFFF); // 获取低16位
                var realAddress = byte.Parse(data["Address"][$"Address{ingredient.DeviceAddress}"]);
                await master.WriteMultipleRegistersAsync(
                    realAddress,
                    0x00FD,
                    [directionAndAcceleration, speed, pulseHigh, pulseLow, 0]);
            }
        }

        using (var digitalControlMaster = IModbusSerialFactory.Create(9600, Parity.Even))
        {
            var coldWaterAndHotWaterAddress = Convert.ToByte(data["BoolInternalAddress"]["ModbusAddress"]);
            switch (address)
            {
                case "冰块":
                {
                    var iceAddress = Convert.ToByte(data["BoolInternalAddress"]["ice"]);
                    await digitalControlMaster.WriteSingleCoilAsync(coldWaterAndHotWaterAddress, iceAddress, true)
                        .ConfigureAwait(true);
                    gram = Convert.ToInt16(turnsDic["冰块"]);
                    ingredient =
                        await _db.Ingredients.FirstOrDefaultAsync(ingredient1 => ingredient1.IngredientName == "冰块");
                    if (ingredient?.TurnsPerGram is null)
                    {
                        await Dispatcher.UIThread.InvokeAsync(() =>
                            _ = MessageBoxUtil.GetBox("未找到该原料").ShowWindowAsync());
                        return;
                    }

                    await Task.Delay(TimeSpan.FromSeconds(gram * ingredient.TurnsPerGram.Value));
                    await digitalControlMaster.WriteSingleCoilAsync(coldWaterAndHotWaterAddress, iceAddress, false)
                        .ConfigureAwait(true);
                    break;
                }
                case "热水":
                {
                    var hotWaterAddress = Convert.ToByte(data["BoolInternalAddress"]["hotWater"]);
                    await digitalControlMaster.WriteSingleCoilAsync(coldWaterAndHotWaterAddress, hotWaterAddress, true)
                        .ConfigureAwait(true);
                    gram = Convert.ToInt16(turnsDic["热水"]);
                    ingredient =
                        await _db.Ingredients.FirstOrDefaultAsync(ingredient1 => ingredient1.IngredientName == "热水");
                    if (ingredient?.TurnsPerGram is null)
                    {
                        await Dispatcher.UIThread.InvokeAsync(() =>
                            _ = MessageBoxUtil.GetBox("未找到该原料").ShowWindowAsync());
                        return;
                    }

                    await Task.Delay(TimeSpan.FromSeconds(gram * ingredient.TurnsPerGram.Value));
                    await digitalControlMaster.WriteSingleCoilAsync(coldWaterAndHotWaterAddress, hotWaterAddress, false)
                        .ConfigureAwait(true);
                    break;
                }
                case "冰水":
                {
                    var coldWaterAddress = Convert.ToByte(data["BoolInternalAddress"]["coldWater"]);
                    await digitalControlMaster.WriteSingleCoilAsync(coldWaterAndHotWaterAddress, coldWaterAddress, true)
                        .ConfigureAwait(true);
                    gram = Convert.ToInt16(turnsDic["冰水"]);
                    ingredient =
                        await _db.Ingredients.FirstOrDefaultAsync(ingredient1 => ingredient1.IngredientName == "冰水");
                    if (ingredient?.TurnsPerGram is null)
                    {
                        await Dispatcher.UIThread.InvokeAsync(() =>
                            _ = MessageBoxUtil.GetBox("未找到该原料").ShowWindowAsync());
                        return;
                    }

                    await Task.Delay(TimeSpan.FromSeconds(gram * ingredient.TurnsPerGram.Value));
                    await digitalControlMaster
                        .WriteSingleCoilAsync(coldWaterAndHotWaterAddress, coldWaterAddress, false)
                        .ConfigureAwait(true);
                    break;
                }
            }
        }
    }

    private Dictionary<object, int> GeInputDictionary()
    {
        var dic = new Dictionary<object, int>(32)
        {
            { 11, int.Parse(Address11Text) },
            { 12, int.Parse(Address12Text) },
            { 13, int.Parse(Address13Text) },
            { 14, int.Parse(Address14Text) },
            { 15, int.Parse(Address15Text) },
            { 16, int.Parse(Address16Text) },
            { 21, int.Parse(Address21Text) },
            { 22, int.Parse(Address22Text) },
            { 23, int.Parse(Address23Text) },
            { 24, int.Parse(Address24Text) },
            { 31, int.Parse(Address31Text) },
            { 32, int.Parse(Address32Text) },
            { 33, int.Parse(Address33Text) },
            { 34, int.Parse(Address34Text) },
            { 41, int.Parse(Address41Text) },
            { 42, int.Parse(Address42Text) },
            { 43, int.Parse(Address43Text) },
            { 44, int.Parse(Address44Text) },
            { 51, int.Parse(Address51Text) },
            { 52, int.Parse(Address52Text) },
            { 53, int.Parse(Address53Text) },
            { 54, int.Parse(Address54Text) },
            { "冰水", int.Parse(ColdWaterText) },
            { "热水", int.Parse(HotWaterText) },
            { "冰块", int.Parse(IceText) },
        };
        return dic;
    }

    private async Task GoIngredientCalibrationAsync(Ingredient ingredient)
    {
        await SetOutSpeedToAddHashSetAsync();
        if (ingredient.Id == -1)
        {
            _ = MessageBoxUtil.GetBox("请选择原料").ShowWindowAsync();
            return;
        }

        if (DeleteHashSet.Count > 0 || AddHashSet.Count > 0)
        {
            var success = await SaveAndUpdateAsync();
            if (success)
                HostScreen.Router.Navigate.Execute(new IngredientCalibrationViewModel(HostScreen, ingredient));
            else
                _ = MessageBoxUtil.GetBox("保存失败").ShowWindowAsync();
        }
        else
        {
            HostScreen.Router.Navigate.Execute(new IngredientCalibrationViewModel(HostScreen, ingredient));
        }
    }

    private async Task<bool> SaveAndUpdateAsync()
    {
        await using var transaction = await _db.Database.BeginTransactionAsync();
        try
        {
            var toDeleteList = await _db.Ingredients.Where(ingredient => DeleteHashSet.Contains(ingredient.Id))
                .ToListAsync();
            _db.Ingredients.UpdateRange(toDeleteList);
            toDeleteList.ForEach(ingredient => ingredient.DeviceAddress = -1);
            await _db.SaveChangesAsync();
            foreach (var ingredient in toDeleteList)
            {
                _db.Entry(ingredient).State = EntityState.Detached;
            }

            _db.Ingredients.UpdateRange(AddHashSet);
            await _db.SaveChangesAsync();
            await transaction.CommitAsync();
            return true;
        }
        catch (Exception)
        {
            await transaction.RollbackAsync();
            return false;
        }
    }

    private async Task SetOutSpeedToAddHashSetAsync()
    {
        var outSpeedDictionary = GetOutSpeedDictionary();
        var addHashSetArray = await _db.Ingredients.ToArrayAsync();
        foreach (var ingredient in addHashSetArray)
        {
            var speed = outSpeedDictionary.GetValueOrDefault(ingredient.DeviceAddress, 200);
            ingredient.OutSpeed = speed;
        }

        _db.Ingredients.UpdateRange(addHashSetArray);
        await _db.SaveChangesAsync();
    }

    private Dictionary<int, int> GetOutSpeedDictionary()
    {
        return new Dictionary<int, int>
        {
            { 11, Address11OutSpeed },
            { 12, Address12OutSpeed },
            { 13, Address13OutSpeed },
            { 14, Address14OutSpeed },
            { 15, Address15OutSpeed },
            { 16, Address16OutSpeed },
            { 21, Address21OutSpeed },
            { 22, Address22OutSpeed },
            { 23, Address23OutSpeed },
            { 24, Address24OutSpeed },
            { 31, Address31OutSpeed },
            { 32, Address32OutSpeed },
            { 33, Address33OutSpeed },
            { 34, Address34OutSpeed },
            { 41, Address41OutSpeed },
            { 42, Address42OutSpeed },
            { 43, Address43OutSpeed },
            { 44, Address44OutSpeed },
            { 51, Address51OutSpeed },
            { 52, Address52OutSpeed },
            { 53, Address53OutSpeed },
            { 54, Address54OutSpeed }
        };
    }

    internal readonly Ingredient Default = new()
    {
        IngredientName = "空",
        DeviceAddress = int.MaxValue,
        Id = -1
    };


    private void InitializeComboBoxSelectedItems()
    {
        var addressIngredientDic = _allIngredientList
            .Where(ingredient => ingredient.DeviceAddress != -1)
            .ToDictionary(ingredient => ingredient.DeviceAddress);

        Address21Selected = addressIngredientDic.GetValueOrDefault(21, Default);
        Address22Selected = addressIngredientDic.GetValueOrDefault(22, Default);
        Address23Selected = addressIngredientDic.GetValueOrDefault(23, Default);
        Address24Selected = addressIngredientDic.GetValueOrDefault(24, Default);
        Address31Selected = addressIngredientDic.GetValueOrDefault(31, Default);
        Address32Selected = addressIngredientDic.GetValueOrDefault(32, Default);
        Address33Selected = addressIngredientDic.GetValueOrDefault(33, Default);
        Address34Selected = addressIngredientDic.GetValueOrDefault(34, Default);
        Address41Selected = addressIngredientDic.GetValueOrDefault(41, Default);
        Address42Selected = addressIngredientDic.GetValueOrDefault(42, Default);
        Address43Selected = addressIngredientDic.GetValueOrDefault(43, Default);
        Address44Selected = addressIngredientDic.GetValueOrDefault(44, Default);
        Address51Selected = addressIngredientDic.GetValueOrDefault(51, Default);
        Address52Selected = addressIngredientDic.GetValueOrDefault(52, Default);
        Address53Selected = addressIngredientDic.GetValueOrDefault(53, Default);
        Address54Selected = addressIngredientDic.GetValueOrDefault(54, Default);
        Address11Selected = addressIngredientDic.GetValueOrDefault(11, Default);
        Address12Selected = addressIngredientDic.GetValueOrDefault(12, Default);
        Address13Selected = addressIngredientDic.GetValueOrDefault(13, Default);
        Address14Selected = addressIngredientDic.GetValueOrDefault(14, Default);
        Address15Selected = addressIngredientDic.GetValueOrDefault(15, Default);
        Address16Selected = addressIngredientDic.GetValueOrDefault(16, Default);
    }

    private ObservableCollection<Ingredient> CreateComboBoxItemIngredientObservableCollection()
    {
        var list = _allIngredientList.ToList();
        list.Add(Default);
        return new ObservableCollection<Ingredient>(list);
    }


    private Task NextPage()
    {
        ShowExtended = true;
        return Task.CompletedTask;
    }

    private Task PrePage()
    {
        ShowExtended = false;
        return Task.CompletedTask;
    }

    private async Task SaveAndQuitAsync()
    {
        await SetOutSpeedToAddHashSetAsync().ConfigureAwait(true);
        if (DeleteHashSet.Count > 0 || AddHashSet.Count > 0)
        {
            var success = await SaveAndUpdateAsync().ConfigureAwait(true);
            if (success)
                HostScreen.Router.NavigateBack.Execute();
            else
                _ = MessageBoxUtil.GetBox("保存失败").ShowWindowAsync();
        }
        else
        {
            HostScreen.Router.NavigateBack.Execute();
        }
    }


    private readonly List<Ingredient> _allIngredientList;
    public string? UrlPathSegment { get; } = nameof(IngredientCalibrationSelectorViewModel);
    public IScreen HostScreen { get; }
    public ReactiveCommand<Unit, Unit> PrePageCmd { get; }
    public ReactiveCommand<Unit, Unit> NextPageCmd { get; }
    public ReactiveCommand<Unit, Unit> SaveAndQuitCmd { get; }
    public ReactiveCommand<Unit, IRoutableViewModel> GoIngredientInputCmd { get; }
    public ReactiveCommand<Ingredient, Unit> GoIngredientCalibrationCmd { get; }
    [Reactive] public bool ShowExtended { get; set; }
    internal ObservableCollection<Ingredient> NotBindAddressIngredientObservableCollection { get; }
    [Reactive] public Ingredient Address21Selected { get; set; }
    [Reactive] public Ingredient Address22Selected { get; set; }
    [Reactive] public Ingredient Address23Selected { get; set; }
    [Reactive] public Ingredient Address24Selected { get; set; }
    [Reactive] public Ingredient Address31Selected { get; set; }
    [Reactive] public Ingredient Address32Selected { get; set; }
    [Reactive] public Ingredient Address33Selected { get; set; }
    [Reactive] public Ingredient Address34Selected { get; set; }
    [Reactive] public Ingredient Address41Selected { get; set; }
    [Reactive] public Ingredient Address42Selected { get; set; }
    [Reactive] public Ingredient Address43Selected { get; set; }
    [Reactive] public Ingredient Address44Selected { get; set; }
    [Reactive] public Ingredient Address51Selected { get; set; }
    [Reactive] public Ingredient Address52Selected { get; set; }
    [Reactive] public Ingredient Address53Selected { get; set; }
    [Reactive] public Ingredient Address54Selected { get; set; }
    [Reactive] public Ingredient Address11Selected { get; set; }
    [Reactive] public Ingredient Address12Selected { get; set; }
    [Reactive] public Ingredient Address13Selected { get; set; }
    [Reactive] public Ingredient Address14Selected { get; set; }
    [Reactive] public Ingredient Address15Selected { get; set; }
    [Reactive] public Ingredient Address16Selected { get; set; }
    internal HashSet<int> DeleteHashSet { get; set; } = new(8);
    internal HashSet<Ingredient> AddHashSet { get; set; } = new(8);
    public ReactiveCommand<Unit, IRoutableViewModel> GoFactorySettingsLoginCmd { get; }
    public ReactiveCommand<string, IRoutableViewModel> GoIngredientCalibrationSpecificTypeCmd { get; }
    [Reactive] public string ColdWaterText { get; set; } = "0";
    [Reactive] public string IceText { get; set; } = "0";
    [Reactive] public string HotWaterText { get; set; } = "0";
    [Reactive] public string Address21Text { get; set; } = "0";
    [Reactive] public string Address22Text { get; set; } = "0";
    [Reactive] public string Address23Text { get; set; } = "0";
    [Reactive] public string Address24Text { get; set; } = "0";
    [Reactive] public string Address31Text { get; set; } = "0";
    [Reactive] public string Address32Text { get; set; } = "0";
    [Reactive] public string Address33Text { get; set; } = "0";
    [Reactive] public string Address34Text { get; set; } = "0";
    [Reactive] public string Address41Text { get; set; } = "0";
    [Reactive] public string Address42Text { get; set; } = "0";
    [Reactive] public string Address43Text { get; set; } = "0";
    [Reactive] public string Address44Text { get; set; } = "0";
    [Reactive] public string Address51Text { get; set; } = "0";
    [Reactive] public string Address52Text { get; set; } = "0";
    [Reactive] public string Address53Text { get; set; } = "0";
    [Reactive] public string Address54Text { get; set; } = "0";
    [Reactive] public string Address11Text { get; set; } = "0";
    [Reactive] public string Address12Text { get; set; } = "0";
    [Reactive] public string Address13Text { get; set; } = "0";
    [Reactive] public string Address14Text { get; set; } = "0";
    [Reactive] public string Address15Text { get; set; } = "0";
    [Reactive] public string Address16Text { get; set; } = "0";
    public ReactiveCommand<string, Unit> OutIngredientCmd { get; }
    [Reactive] public int Address21OutSpeed { get; set; }
    [Reactive] public int Address22OutSpeed { get; set; }
    [Reactive] public int Address23OutSpeed { get; set; }
    [Reactive] public int Address24OutSpeed { get; set; }
    [Reactive] public int Address31OutSpeed { get; set; }
    [Reactive] public int Address32OutSpeed { get; set; }
    [Reactive] public int Address33OutSpeed { get; set; }
    [Reactive] public int Address34OutSpeed { get; set; }
    [Reactive] public int Address41OutSpeed { get; set; }
    [Reactive] public int Address42OutSpeed { get; set; }
    [Reactive] public int Address43OutSpeed { get; set; }
    [Reactive] public int Address44OutSpeed { get; set; }
    [Reactive] public int Address51OutSpeed { get; set; }
    [Reactive] public int Address52OutSpeed { get; set; }
    [Reactive] public int Address53OutSpeed { get; set; }
    [Reactive] public int Address54OutSpeed { get; set; }
    [Reactive] public int Address11OutSpeed { get; set; }
    [Reactive] public int Address12OutSpeed { get; set; }
    [Reactive] public int Address13OutSpeed { get; set; }
    [Reactive] public int Address14OutSpeed { get; set; }
    [Reactive] public int Address15OutSpeed { get; set; }
    [Reactive] public int Address16OutSpeed { get; set; }

    public List<Ingredient> CheckSelectedList(Ingredient ingredientAdd)
    {
        return AddToList()
            .Where(ingredient => ingredient.Id != -1 && ingredient.Id == ingredientAdd.Id)
            .ToList();
    }

    private List<Ingredient> AddToList()
    {
        return
        [
            Address21Selected, Address22Selected, Address23Selected, Address24Selected, Address31Selected,
            Address32Selected, Address33Selected, Address34Selected, Address41Selected, Address42Selected,
            Address43Selected, Address44Selected, Address51Selected, Address52Selected, Address53Selected,
            Address54Selected, Address11Selected, Address12Selected, Address13Selected, Address14Selected,
            Address15Selected, Address16Selected
        ];
    }
}